package com.atguigu.gmall.activity.task;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.util.DateUtil;
import com.atguigu.gmall.activity.util.SeckillUtil;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.base.BaseEntity;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/***
 * 定时任务
 */
@Component
public class ScheduledTask {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Resource
    private SeckillGoodsMapper seckillGoodsMapper;

    /**
     * 定时将秒杀的商品写入redis中：数据预热
     * cron表达式：秒 分 时 日 月 周 年
     * * ： 表示任意
     * ？ : 忽略这个单位
     * , : 并列
     * - : 区间
     * / : 间隔
     *
     * fixedRate: 每多少秒执行一次，单位时毫秒
     * fixedDelay: 受方法执行的时间影响
     */
    @Scheduled(cron = "1/60 * * * * *")
    public void addSeckillGoodsIntoRedis() throws Exception{
        //1 根据当前时间，生成后续的几个时间
        List<Date> dateMenus = DateUtil.getDateMenus();
        dateMenus.stream().forEach(date -> {
            //获取开始时间，转换格式 "yyyy-MM-dd HH:mm"
            String startTime = DateUtil.data2str(date, DateUtil.PATTERN_YYYY_MM_DDHHMM);
            //获取结束时间，转换格式 "yyyy-MM-dd HH:mm"
            Date endDate = DateUtil.addDateHour(date, 2);
            String endTime = DateUtil.data2str(endDate, DateUtil.PATTERN_YYYY_MM_DDHHMM);
            //获取存入redis中的时间段字符串
            String timeKey = DateUtil.data2str(date, DateUtil.PATTERN_YYYYMMDDHH);

            //获取当前时间
            long currentTimeMillis = System.currentTimeMillis();
            //计算redis中key的过期时间，活动结束就过期
            long liveTime = endDate.getTime() - currentTimeMillis;

            //2 根据不同的时间段，获取数据 --> 指定查询条件
            LambdaQueryWrapper<SeckillGoods> wrapper = new LambdaQueryWrapper<>();
            //审核状态
            wrapper.eq(SeckillGoods::getStatus,1);
            //开始时间
            wrapper.le(SeckillGoods::getStartTime,startTime);
            //结束时间
            wrapper.ge(SeckillGoods::getEndTime,endTime);
            //剩余库存
            wrapper.gt(SeckillGoods::getStockCount,0);
            //把redis中有的商品排除
            //获取当前时间段所有的商品id
            Set seckillGoodsIds = redisTemplate.opsForHash().keys(timeKey);
            if (seckillGoodsIds != null && seckillGoodsIds.size() > 0) {
                //对应id的商品存在，则排除
                wrapper.notIn(BaseEntity::getId,seckillGoodsIds);
            }

            //3 获取满足查询条件的参与秒杀的商品集合
            List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectList(wrapper);
            //遍历
            seckillGoodsList.stream().forEach(seckillGoods -> {
                //4 写入redis中 使用reids的hashey存时间段数据类型，k，hkey存商品的id，hvalue存商品信息
                //获取秒杀商品id
                Long seckillGoodsId = seckillGoods.getId();
                //写入 key:2023031322 value : 商品id --> 具体商品信息
                redisTemplate.opsForHash().put(timeKey, seckillGoodsId+ "",seckillGoods);

                //构建一个商品剩余数量长度的List数据类型，用来判断是否有库存
                //获取商品的剩余库存
                Integer stockCount = seckillGoods.getStockCount();
                //获取商品剩余数量长度的数组
                String[] goodsIds = SeckillUtil.getseckillGoodsIdArray(stockCount,seckillGoodsId + "");
                redisTemplate.opsForList().
                        leftPushAll("seckill_goods_stock_count_queue_" + seckillGoodsId,goodsIds);
                //设置过期时间，因为key是商品id，所以每个key都需设置
                redisTemplate.expire("seckill_goods_stock_count_queue_" + seckillGoodsId,liveTime, TimeUnit.MILLISECONDS);

                //构建商品剩余库存的自增值：自增商品剩余库存数量 --> a.记录商品的剩余库存 b.同步数据库的数据
                redisTemplate.opsForHash()
                        .increment("seckill_goods_stock_count_" + timeKey,
                                seckillGoodsId+ "",
                                stockCount);
            });
            //设置每个时间段商品的key的过期时间
            this.setSeckillGoodsInfoExpire(timeKey,liveTime);
        });
    }

    /**
     * 设置商品的过期时间和库存自增值过期
     * @param timeKey
     * @param liveTime
     */
    private void setSeckillGoodsInfoExpire(String timeKey, long liveTime) {
        //控制一个时间段的商品key只设置一次过期-->每个时间段做自增记录，实际是看定时任务执行了几次
        Long incrementCount =
                redisTemplate.opsForHash().increment("seckill_goods_times_expire",timeKey, 1);
        if(incrementCount > 1){
            return;
        }

        //设置商品的过期时间
        redisTemplate.expire(timeKey,liveTime, TimeUnit.MILLISECONDS);

        //因为库存自增值的key需要需用用来同步MySQL中的数据，所以要到同步完MySQL中的数据后才清除
        //redis的库存和数据库的库存同步:发送延迟消息,在商品活动结束半小时后开始同步商品库存
        rabbitTemplate.convertAndSend("seckill_goods_delay_exchange",
                "seckill.goods.delay",
                timeKey,
                message -> {
                    MessageProperties properties = message.getMessageProperties();
                    properties.setExpiration(180000 + liveTime + "");
//                    properties.setExpiration(30000 + "");
                    return message;
                });
    }
}
